package com.erbao.nettystudy.s03;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.ReferenceCountUtil;

import java.util.Objects;

/**
 * netty 自动多线程，多线程封装在EventLoopGroup
 * 在s01的基础上封装，方便窗口调用
 * @author CaoBaoYin
 * @since 2021/10/21 7:46
 */
public class Client {

    private Channel channel;
    public static void main(String[] args) {
        new Client().connect();
    }

    public void connect() {
        /*循环事件不停处理的线程，形成一个组、池子。
        * 客户端传参1个线程即可。
        * 线程池。用来处理整个channel上的所有事件*/
        EventLoopGroup group = new NioEventLoopGroup(1);

        // 启一个socket连接其他服务器，辅助启动类
        Bootstrap bootstrap = new Bootstrap();
        try {
            // 远程有server 这句就连上了
            /*bootstrap.group(group)// 启动需要的线程池
                    .channel(NioSocketChannel.class)// 指定连接通道类型
                    .handler(new ClientChannelInitializer()) // 事件处理
                    .connect("localhost", 8888)// 是异步方法
                    .sync();*///确认connect完成之后，再往下执行就需要调用sync
            /*上述方法调用的sync 是 connect 返回的future*/
            // 这是连接的第二种写法
            ChannelFuture future = bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientChannelInitializer())
                    .connect("localhost", 8888);
            future.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture f) throws Exception {
                    if (f.isSuccess()) {
                        System.out.println("connected successful");
                        channel = future.channel();
                    } else {
                        System.out.println("not connected");
                    }
                }
            });

            future.sync();

            future.channel().closeFuture().sync();
            System.out.println("程序即将结束。。。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 客户端优雅地结束
            group.shutdownGracefully();
        }
    }

    /**
     * 发送消息到服务端
     * @param msg 消息
     */
    public void sendMsg (String msg) {
        Objects.requireNonNull(channel, "客户端未连接到server，通道尚未建立，不能发送消息");
        ByteBuf byteBuf = Unpooled.copiedBuffer(msg.getBytes());
        channel.writeAndFlush(byteBuf);
    }

    public void closeConnect(){
        sendMsg("_bye_");
    }
}

class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {

    /*当channel连接之后，会调用，说明channel已经可用*/
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        System.out.println(ch);
        ch.pipeline()
                .addLast(new TankMsgEncoder())
                .addLast(new ClientHandler());// 业务逻辑处理加最后
    }
}

class ClientHandler extends ChannelInboundHandlerAdapter {// 传进来的handler 编码是传出去
    // 第一次初始化的时候，channel已经可用了
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // channel第一次连上可用 写出一个字符串. netty写数据都是由ByteBuf写出去的
        // 平常数据是jvm内存和操作系统内存之间拷贝。ByteBuf 是 Direct Memory（直接访问内存），效率更好，但跳过了jvm垃圾回收，需要自己释放内存
        // ByteBuf buf = Unpooled.copiedBuffer("hello".getBytes());
        // ctx.writeAndFlush(buf);// write 结束自动释放内存
        ctx.writeAndFlush(new TankMsg(5, 8)); // 加了编码器，就可以直接写对象
    }

    // 接收数据使用。
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = null;
        try {
            buf = (ByteBuf) msg;
            System.out.println(buf);
            // System.out.println(buf.refCnt()); // 1 多少指向（引用）了它
            byte[] bytes = new byte[buf.readableBytes()];//可读长度
            buf.getBytes(buf.readerIndex(), bytes);// 从可读位置，读满整个字节数组
            ClientFrame.INSTANCE.updateFrameTa(new String(bytes));
        } finally {
            if (buf != null) {
                ReferenceCountUtil.release(buf);
            }
            // ctx.channel().close();
        }
        // System.out.println(buf.refCnt());// 0
    }
}
